﻿using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using TiffLibrary;
using TiffLibrary.ImageEncoder;
using TiffLibrary.PixelFormats;
using WholeSlideImageLibrary;

namespace WholeSlideImageViewer.TiffExport
{
    internal class TiffExporter : IAsyncDisposable
    {
        // Options
        private readonly TiffExportOptions _options;

        // Source
        private WholeSlideImage? _image;

        // Tiff file related
        private readonly string _outputFile;
        private Stream? _stream;
        private TiffFileContentReaderWriter? _readerWriter;
        private TiffFileWriter? _fileWriter;
        private TiffFileReader? _fileReader;
        private readonly TiffImageEncoder<TiffRgba32> _encoder;

        // Progress report
        private readonly TiffExportProgress _progress;

        public TiffExportProgress Progress => _progress;

        public TiffExporter(ITiffOutputOptions options, string outputFile)
        {
            _options = TiffExportOptions.Create(options);
            _options.Software = "WholeSlideViewer";

            _outputFile = outputFile;

            var builder = new TiffImageEncoderBuilder();
            if (options.Compression == TiffCompression.Jpeg)
            {
                builder.PhotometricInterpretation = TiffPhotometricInterpretation.YCbCr;
                builder.Compression = TiffCompression.Jpeg;
                builder.HorizontalChromaSubSampling = 2;
                builder.VerticalChromaSubSampling = 2;
                builder.JpegOptions = new TiffJpegEncodingOptions
                {
                    OptimizeCoding = options.JpegOptimizeCoding,
                    UseSharedQuantizationTables = options.JpegUseSharedQuantizationTables,
                    UseSharedHuffmanTables = options.JpegUseSharedHuffmanTables
                };
            }
            else
            {
                builder.PhotometricInterpretation = TiffPhotometricInterpretation.RGB;
                builder.Compression = options.Compression;
            }
            builder.IsTiled = true;
            builder.TileSize = new TiffSize(options.TileSize, options.TileSize);
            builder.MaxDegreeOfParallelism = options.ThreadCount;

            _encoder = builder.Build<TiffRgba32>();

            _progress = new TiffExportProgress();
        }

        public async Task InitializeAsync(WholeSlideImage image, CancellationToken cancellationToken)
        {
            _image = image;

            _stream = new FileStream(_outputFile, FileMode.Create, FileAccess.ReadWrite, FileShare.None, 4096, true);
            _readerWriter = new StreamContentReaderWriter(_stream);
            _fileWriter = await TiffFileWriter.OpenAsync(_readerWriter, leaveOpen: true, useBigTiff: true, cancellationToken: cancellationToken).ConfigureAwait(false);
        }

        public async Task RunAsync(CancellationToken cancellationToken)
        {
            if (_image is null)
            {
                throw new InvalidOperationException();
            }

            int outputTileSize = _options.TileSize;

            try
            {
                // 推算总的Tile数量
                int totalTileCount = 0;
                int calcWidth = _image.Width, calcHeight = _image.Height;
                while (Math.Min(calcWidth, calcHeight) >= outputTileSize && Math.Min(calcWidth, calcHeight) >= 32)
                {
                    int rowCount = (calcWidth + outputTileSize - 1) / outputTileSize;
                    int colCount = (calcHeight + outputTileSize - 1) / outputTileSize;
                    totalTileCount += rowCount * colCount;

                    calcWidth = (calcWidth + 1) / 2;
                    calcHeight = (calcHeight + 1) / 2;
                }
                _progress.NotifyTileCountEstimated(totalTileCount);

                // 输出基础层
                TiffStreamOffset ifdOffset = await RipBaseLayerAsync(cancellationToken).ConfigureAwait(false);

                // 输出缩小层
                await GenerateReducedResolutionLayersAsync(ifdOffset, cancellationToken).ConfigureAwait(false);

                _progress.NotifyCompleted();

                if (!(_fileWriter is null))
                {
                    await _fileWriter.FlushAsync().ConfigureAwait(false);
                    await _fileWriter.DisposeAsync().ConfigureAwait(false);
                    _fileWriter = null;
                }
                if (!(_fileReader is null))
                {
                    await _fileReader.DisposeAsync().ConfigureAwait(false);
                    _fileReader = null;
                }
                if (!(_readerWriter is null))
                {
                    await _readerWriter.DisposeAsync().ConfigureAwait(false);
                    _readerWriter = null;
                }
                if (!(_stream is null))
                {
                    await _stream.DisposeAsync().ConfigureAwait(false);
                    _stream = null;
                }
            }
            catch (OperationCanceledException)
            {
                _progress.NotifyCanceled();
            }
            catch (Exception)
            {
                _progress.NotifyFailed();
            }
            finally
            {
                if (!(_readerWriter is null))
                {
                    await _readerWriter.DisposeAsync().ConfigureAwait(false);
                    _readerWriter = null;
                }
                if (!(_stream is null))
                {
                    await _stream.DisposeAsync().ConfigureAwait(false);
                    File.Delete(_outputFile);
                }
            }
        }

        private async Task<TiffStreamOffset> RipBaseLayerAsync(CancellationToken cancellationToken)
        {
            if (_image is null || _fileWriter is null)
            {
                throw new InvalidOperationException();
            }

            var regionReader = new TileRegionReader(_image, _progress);

            using TiffImageFileDirectoryWriter? ifdWriter = _fileWriter.CreateImageFileDirectory();
            await _encoder.EncodeAsync(ifdWriter, regionReader, cancellationToken).ConfigureAwait(false);

            TiffStreamOffset ifdOffset = await ifdWriter.FlushAsync(cancellationToken).ConfigureAwait(false);
            _fileWriter.SetFirstImageFileDirectoryOffset(ifdOffset);
            await _fileWriter.FlushAsync(cancellationToken).ConfigureAwait(false);
            return ifdOffset;
        }

        private async Task GenerateReducedResolutionLayersAsync(TiffStreamOffset ifd, CancellationToken cancellationToken)
        {
            if (_readerWriter is null || _image is null)
            {
                throw new InvalidOperationException();
            }

            _fileReader = await TiffFileReader.OpenAsync(new StreamContentSource(_readerWriter), leaveOpen: true, cancellationToken: cancellationToken);

            int width = _image.Width;
            int height = _image.Height;
            int outputTileSize = _options.TileSize;

            // Actually generate the layers
            while (Math.Min(width, height) >= outputTileSize && Math.Min(width, height) >= 32)
            {
                cancellationToken.ThrowIfCancellationRequested();

                ifd = await GenerateReducedResolutionLayerAsync(ifd, cancellationToken).ConfigureAwait(false);

                width = (width + 1) / 2;
                height = (height + 1) / 2;
            }
        }

        private async Task<TiffStreamOffset> GenerateReducedResolutionLayerAsync(TiffStreamOffset ifdOffset, CancellationToken cancellationToken)
        {
            if (_fileReader is null || _fileWriter is null)
            {
                throw new InvalidOperationException();
            }

            TiffImageDecoder image = await _fileReader.CreateImageDecoderAsync(ifdOffset, cancellationToken).ConfigureAwait(false);

            using TiffImageFileDirectoryWriter? ifdWriter = _fileWriter.CreateImageFileDirectory();
            await _encoder.EncodeAsync(ifdWriter, new ReducedResolutionLayerReader(image, _progress), cancellationToken).ConfigureAwait(false);

            await ifdWriter.WriteTagAsync(TiffTag.NewSubfileType, new TiffValueCollection<ushort>((ushort)TiffNewSubfileType.ReducedResolution), cancellationToken).ConfigureAwait(false);

            return await ifdWriter.FlushAsync(ifdOffset, cancellationToken).ConfigureAwait(false);
        }

        public async ValueTask DisposeAsync()
        {
            if (!(_fileReader is null))
            {
                await _fileReader.DisposeAsync().ConfigureAwait(false);
            }
            if (!(_fileWriter is null))
            {
                await _fileWriter.FlushAsync().ConfigureAwait(false);
                await _fileWriter.DisposeAsync().ConfigureAwait(false);
            }
            if (!(_stream is null))
            {
                await _stream.DisposeAsync().ConfigureAwait(false);
            }
        }
    }
}
